/*
 * SarsPacket.cs
 * Definiton of SARS Packet object.
 */

using System;
using System.Collections.Generic;
using ICSharpCode.SharpZipLib.GZip;
using System.IO;


namespace LibSars
{
	public class SarsPacket {
		// Minimum packet length in bytes
		public const int MinByteLength = 24;
		// Message object, has always value assigned to (NULL Object Pattern!)
		SarsPacketMessage content = SarsPacketMessageURPSEUDO.Instance;
		// Sender ID
		public UInt16 SenderId = 0;
		// Recipient ID
		public UInt16 RecipientId = 0;
		// Timestamp
		public UInt32 Timestamp = 0;
		// Channel id for packet
		public byte Channel;
		// Compression enabled
		byte bitFlags = 0x00;
		
		public byte BitFlags {
			get { return bitFlags; }
		}
		public bool HyperCompress {
			get { return ( (bitFlags & 0x01) == 0x01)?true:false; }
			set { if (value == true) bitFlags |= 0x01; else bitFlags &= 0xFE; }
		}

		
		
		// Property for payload message object.
		// Null or invalid values are not allowed (NULL Object Pattern!).
		public SarsPacketMessage Message {
			set {
				if (value == null || !(value is SarsPacketMessage)){
					content = SarsPacketMessageURPSEUDO.Instance;
					return ;
				}
				
				content = value;
			}
			get { return content; }
		}
		
		// Generate header
		private void GenerateBytesForHeader(ref List<byte> byteList){
			// Write SARS Packet identifier
			byteList.AddRange(System.Text.Encoding.ASCII.GetBytes("SARS"));
			                  
			// Add protocol version
			byteList.Add(0x00);
			
			// Add 1 byte for bit flags
			byteList.Add(bitFlags);
			
			
			
			// Add sender id (2 bytes)
			byteList.AddRange(UInt16ToByteArray(SenderId));

			// Add recipient id (2 bytes)
			byteList.AddRange(UInt16ToByteArray(RecipientId));
			
			// Add 1 byte padding - used for bit options
			byteList.Add(Channel);
			
			byteList.Add(0x00);
			byteList.Add(0x00);
			byteList.Add(0x00);
			
			// Add timestamp (4 bytes)
			byteList.AddRange(UInt32ToByteArray(Timestamp));
			
			
			
			// Add message type (2 bytes)
			UInt16 mtype = Message.GetMTypeInt();
			byteList.AddRange(UInt16ToByteArray(mtype));
			
		}
		
		// Convert unsigned 16 bit to byte array
		public byte[] UInt16ToByteArray(UInt16 val){
			byte[]ret = new Byte[2];
			ret[0] = ( (byte)((val >>  8) & 0xFF) ); // higher byte first
			ret[1] = ( (byte)((val      ) & 0xFF) ); // lowest byte last
			return ret;
		}
		
		// Convert unsigned 32 bit to byte array
		public byte[] UInt32ToByteArray(UInt32 val){
			byte[]ret = new Byte[4];
			ret[0] = ( (byte)((val >> 24) & 0xFF) ); // higher byte first
			ret[1] = ( (byte)((val >> 16) & 0xFF) ); // 
			ret[2] = ( (byte)((val >>  8) & 0xFF) ); // 
			ret[3] = ( (byte)((val      ) & 0xFF) ); // lowest byte last
			return ret;
		}
		
		// Convert packet into byte stream
		public byte[] GenerateBytes(){
			// Temporary list for saving package bytes
			List<byte> byteList = new List<byte>();
			
			// Generate header as bytes
			GenerateBytesForHeader(ref byteList);
			
			// Generate payload
			byte[] payload = Message.GeneratePayloadBytes();
			
			// Compress payload if enabled
			if (HyperCompress){
				
				MemoryStream memStream = new MemoryStream();

				GZipOutputStream gzCompressed = new GZipOutputStream(memStream);
				gzCompressed.Write(payload, 0, payload.Length);
				gzCompressed.Finish();
				gzCompressed.Flush();

				payload = memStream.ToArray();
			}

			// Add payload length
			byteList.AddRange(UInt32ToByteArray((UInt32)payload.Length));
			// Add payload
			byteList.AddRange(payload);
			
			return byteList.ToArray();
		}
		
		// String represantation.
		public override string ToString(){
			System.Text.StringBuilder sb = new System.Text.StringBuilder();
			sb.Append("Raw: [");
			byte[] barr = this.GenerateBytes();
			for(int i=0; i<barr.Length; ++i){
				if (i % 2 == 0 && i > 0)
					sb.Append(" ");
				sb.Append(barr[i].ToString("X").PadLeft(2, '0'));
			}
			sb.Append("]"+Environment.NewLine);
			sb.AppendFormat("Sender: {0}{1}",this.SenderId,Environment.NewLine);
			sb.AppendFormat("Recipient: {0}{1}",this.RecipientId,Environment.NewLine);
			sb.AppendFormat("BitFlags: {0}{1}",this.BitFlags,Environment.NewLine);
			sb.AppendFormat("MessageType: {0}{1}",this.Message.GetMTypeInt(),Environment.NewLine);
			return sb.ToString();
		}
		
		
		// Generate SARS packet from raw bytes.
		public static SarsPacket GenerateFrom(byte[] bytes, int startId){
			if(SarsPacketByteValidator.FastValidate(bytes,startId) >= 0){
				UInt16 mtype = (UInt16)((bytes[startId+18] << 8) | bytes[startId+19]);
				int readPayloadLen = (Int32)( (bytes[startId+20] << 24) | (bytes[startId+21] << 16) | (bytes[startId+22] << 8) | bytes[startId+23]);
				byte[] payload = new byte[readPayloadLen];

				// copy payload bytes
				for(int i=0; i<readPayloadLen; ++i)
					payload[startId+i] = bytes[startId+24+i];
				
				SarsPacket newPkg = new SarsPacket();
				
				newPkg.bitFlags = bytes[startId+5];
				newPkg.SenderId = (UInt16)((bytes[startId+6] << 8) | bytes[startId+7]);
				newPkg.RecipientId = (UInt16)((bytes[startId+8] << 8) | bytes[startId+9]);
				newPkg.Channel = bytes[startId+10];
				
				if(newPkg.HyperCompress){
					MemoryStream memStream = new MemoryStream(payload);
					

	
					GZipInputStream gzDecompressed = new GZipInputStream(memStream);
					
					try {
						payload = new byte[gzDecompressed.Length];
						gzDecompressed.Read(payload,0,(int)gzDecompressed.Length);
					} catch {
						mtype = SarsPacketMessageURPSEUDO.Instance.GetMTypeInt();
					}
				}
				newPkg.Message = SarsPacketMessageImporter.GenerateMessageFrom(mtype,ref payload);
				
			
				return newPkg;
			}
			return null;
		}
		public static SarsPacket GenerateFrom(byte[] bytes){
			return GenerateFrom(bytes,0);
		}
		
	}
}
